package com.gopay.scheduler.job;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.gopay.common.cipher.utils.LoggerMaskUtils;
import com.gopay.common.domain.user.identify.PersCertChannelDTL;
import com.gopay.common.user.manager.PersonThreeAccountChannelManager;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.fastjson.JSON;
import com.gopay.common.cipher.utils.LoggerMaskUtils;
import com.gopay.common.constants.cust.CustPersonInfoConstants;
import com.gopay.common.domain.cust.CustPersonInfo;
import com.gopay.common.exception.GopayException;
import com.gopay.common.user.dao.CustPersonInfoQueryDAO;
import com.gopay.common.user.dao.PersCertChannelDtlDao;
import com.gopay.scheduler.GopayBaseJob;
import com.gopay.scheduler.job.vo.CertificationReqParamVo;
import com.opendata.api.ODPRequest;

import javax.annotation.Resource;

/**
 * 首次登录实名认证成功且开户成功的用户，系统自动调用其它不需用户配合的认证渠道接口，
 * 认证成功后渠道数+1，账户类别按规则变二类或三类
 * CustChannelCertifyJob.java
 * @author zhaojunting
 * @2016年5月10日 上午10:05:25  www.gopay.com.cn Inc.All rights reserved.
 */
public class CustChannelCertifyJob extends GopayBaseJob {

    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    /* 实名认证：航空渠道代码  */
    public static final String CERT_CODE_HANGKONG = "02";
    /* 实名认证：成功返回数据  */
    public static final String CERT_RESULT_SUCCESS = "1";
    /* 实名认证：没有数据返回  */
    public static final String CERT_RESULT_NO_RETURN = "0";
    /* 实名认证：出现异常  */
    public static final String CERT_RESULT_EX = "-1";
    
    /*操作系统换行符*/
    public static final String LINE_SEPARATOR = System.getProperty("line.separator");
    
    /* 一次批处理条数（CustPersonInfo表 pageSize）  */
    private int batchSize = 1000;
    
    /* 实名认证接口参数     */
    private CertificationReqParamVo certificationReqParam;
    
    
    
    @Autowired
    private CustPersonInfoQueryDAO custPersonInfoQueryDAO;
    
    @Autowired
    private PersCertChannelDtlDao persCertChannelDtlDao;
    
//    @Autowired
//    private PersonIdentifyDAO personIdentifyDAO;

    @Resource(name = "personThreeAccountChannelManager")
    private PersonThreeAccountChannelManager personThreeAccountChannelManager;
    

    @Override
    protected void doJobInternal(){
    	//验证实名认证接口参数初始化
        if(!verifyCertificationParam()){
            return ;
        }
        
        //1.获取已实名已认证客户总数（未认证海航渠道）
        int count = getCertificationCounts();
        if (count == 0) {
            return;
        }
        
        //2.批处理
        int i = (count + batchSize - 1) / batchSize ;     //总批次
        
        for (int j = 0; j < i; j++) {
            log.info("进行客户实名认证批处理： 当前批次：j = {}, 总批次 : i = {}, 总数： count = "+count+" ; ", j, i);
            //3.分页查询实名认证成功且开户成功的用户（未认证海航渠道）
            List<CustPersonInfo> custs = findCertifiedCustsByPage(j + 1);   //pageIndex = j + 1
                   
            //4.渠道认证
            doBatchChannelCertify(custs);
        }
        
        log.info("客户实名认证批处理完成： 总批次 : i = {}; ", i);
    }
    
    /**
     * 注意是账户对应类别   不是身份证对应类别
     * 获取已实名已认证的个人客户数量（未认证海航渠道）
     * @return  
     */
    private int getCertificationCounts(){        
        return custPersonInfoQueryDAO.getCertCustCountsByChannel(CERT_CODE_HANGKONG);
    }
    
    /**
     * 分页查询已实名已认证个人客户（未认证海航渠道）
     * @param pageIndex
     * @return
     */
    private List<CustPersonInfo> findCertifiedCustsByPage(int pageIndex){
        return custPersonInfoQueryDAO.findCertCustsByPageAndChannel(pageIndex, batchSize, CERT_CODE_HANGKONG);
    }
    
    /**
     * 对批客户进行渠道认证
     * @param custs 
     * @throws GopayException 
     */
    private void doBatchChannelCertify(List<CustPersonInfo> custs) {
        for(CustPersonInfo cust : custs){
            //add by songfengli 20180312 判断是否需要调用第三方服务
            if(StringUtils.isNotBlank(cust.getCertNo())){
                if(!personThreeAccountChannelManager.isTransferCertChannel(cust.getCertNo(),cust.getCustId())){
                    continue;
                }
                doChannelCertify(cust);
            }
        }
    }
    
    /**
     * 进行客户渠道认证
     * @param cust
     * @throws GopayException 
     */
    private void doChannelCertify(CustPersonInfo cust){
        if(!CustPersonInfoConstants.REAL_NAME_CERTIFY_YES.equals(cust.getIsRealNameCertify())){
            return ;
        }
        
        if(!hasDoQueryCertification(cust)){
            //调用航空实名认证接口
            doQueryCertification(cust);
        }
        
    }

    /**
     * 判断该用户是否成功调用过 航空实名认证接口
     * @param cust
     * @return
     */
    private boolean hasDoQueryCertification(CustPersonInfo cust){
        boolean has = persCertChannelDtlDao.hasDoQueryCertification(cust);
        
        log.info("查询客户是否调用过航空实名认证接口： has = {}," + simpleCustInfo(cust), has);
        return has;
    }
    /**
     * 验证实名认证接口参数初始化
     * @return
     * @throws GopayException 
     */
    private boolean verifyCertificationParam(){
        if(certificationReqParam == null){
            log.warn("航空实名认证接口参数尚未初始化！");
            throw new RuntimeException("航空实名认证接口参数尚未初始化!");
        }
        boolean validate = certificationReqParam.validate();
        log.info("实名认证参数验证 ：  " + LoggerMaskUtils.maskString(certificationReqParam.toString()));
        return validate;
    }
    
    /**
     * 航空实名认证接口
     * @param cust  客户信息
     * @throws GopayException 
     */
    private void doQueryCertification(CustPersonInfo cust) {
        //1.调用实名认证请求
        String res = doQueryCertificationReq(cust);
        
        //2.解析认证响应对象
        Map rspMsg = JSON.parseObject(res, Map.class);
        //解析响应信息，并记录日志   
        Map rspInfo = getRspInfoFromCertificationRsp(rspMsg);
        
        //3.接口调用失败，不写库，记录日志并返回
        if(null == rspMsg || null == rspInfo || !CERT_RESULT_SUCCESS.equals(rspInfo.get("result"))){
            log.info("航空实名认证接口调用失败 ：rspInfo = {}" + LINE_SEPARATOR, rspInfo);
            return;
        }
        
        //4.接口调用成功， 解析响应数据
        Map dataRoot = getDataRootFromCertificationRsp(rspMsg);
        if(dataRoot == null || dataRoot.size() == 0){
            //edit by zhaojt 2016.09.23 对响应数据结构做兼容处理（当乘机记录为一条时，TcCertFltCustDs子节点被去掉，直接从DataRoot结点拿取数据）
            Map data = getDataFromCertificationRsp(rspMsg);
            if(data == null || data.size() == 0) {
                log.warn("实名认证接口调用成功, DataRoot节点为空！" + LINE_SEPARATOR);
                return ;
            }else{
                dataRoot = data; //兼容处理
            }
        }
        
        List tcCertFltCustDs = getTcCertFltCustDsFromDataRoot(dataRoot);//渠道认证信息
        if(tcCertFltCustDs == null || tcCertFltCustDs.size() == 0){
            log.warn("实名认证接口调用成功, TcCertFltCustD节点为空！" + LINE_SEPARATOR);
            return ;
        }
        
        //5.获取到各渠道认证信息
        if(isCertificationSuccess(tcCertFltCustDs)){
            //判断更新账户类别
            certificationSuccess(cust, CERT_CODE_HANGKONG);
        }else{
            certificationFail(cust, CERT_CODE_HANGKONG);
        }
        log.info("航空实名认证接口完成调用." + LINE_SEPARATOR);
    }
    
    /**
     * 实名认证成功，记录实名认证信息表，并判更账户类别
     * @param cust
     * @param channel
     */
    private void certificationSuccess(CustPersonInfo cust, String channel){
        //账户类别绑定 账号，不在关联身份证！
//        //1.查询个人用户身份类别 信息
//        PersIdUsedQuota identityInfo = personIdentifyDAO.getIdentityInfo(cust.getCertNo());
//        
//        //2.查询个人用户已验证渠道数量
//        int count = persCertChannelDtlDao.findSuccessCertChannelCountsByCustId(cust.getCustId());
//        count++;    //此次认证成功+1
//        
//        //3.根据规则判定是否更新账户类别
//        if(count >= 5){
//            //更新为三类账户
//            personIdentifyDAO.update(identityInfo);
//        }
//        else if(count >= 3){
//            //更新为二类账户
//            personIdentifyDAO.update(identityInfo);
//        }
        
        //4.记录渠道认证信息
        personThreeAccountChannelManager.savePersCertChannel(buildPersCertChannelDTL(cust, channel, true));//成功
        //persCertChannelDtlDao.addCertChannelDtl(cust, channel, true);//成功
        log.info("实名认证成功 , 入库。");
    }
    
    /**
     * 实名认证失败, 插入认证失败信息
     */
    private void certificationFail(CustPersonInfo cust, String channel){
        personThreeAccountChannelManager.savePersCertChannel(buildPersCertChannelDTL(cust, channel, false));//失败
        //persCertChannelDtlDao.addCertChannelDtl(cust, channel, false);//失败
        
        log.info("实名认证失败  , 入库。");
    }
    
    /**
     * 获取实名认证接口响应码
     * @param rsp
     * @return
     */
    private String getResultFromCertificationRsp(Map rspMsg){
        Object result = getNodeByPath("msgResponse.responseInfo.result", rspMsg);
        
        return (result instanceof String) ? (String)result : null;
    }
    
    /**
     * 获取实名认证接口响应信息对象,并记录日志
     * @param rspMsg
     * @return
     */
    private Map getRspInfoFromCertificationRsp(Map rspMsg){
        Object responseInfo = getNodeByPath("msgResponse.responseInfo", rspMsg);
        
        if(responseInfo instanceof Map){
            log.info("实名认证接口responseInfo: " + LoggerMaskUtils.maskString(responseInfo.toString()));
            return (Map)responseInfo;
        }
        
        log.warn("实名认证接口解析msgResponse.responseInfo路径节点-->类型转换失败!");
        return null;
    }
    
    /**
     * 取得实名认证响应数据DataRoot节点 
     * @param rspMsg
     * @return
     */
    private Map getDataRootFromCertificationRsp(Map rspMsg){
        Object dataRoot = getNodeByPath("msgResponse.data.DataRoot", rspMsg);
        
        return (dataRoot instanceof Map) ? (Map)dataRoot : null;
    }
    /**
     * 取得实名认证响应数据Data节点
     * @param rspMsg
     * @return
     */
    private Map getDataFromCertificationRsp(Map rspMsg){
        Object dataRoot = getNodeByPath("msgResponse.data", rspMsg);
        return (dataRoot instanceof Map) ? (Map)dataRoot : null;
    }
    /**
     * 在DataRoot节点中获取 TcCertFltCustD 列表
            "DataRoot" : {
                "TcCertFltCustD" : [ {
                    "COM_CD" : "JD",
                    "RESULT" : "无"
                }, {
                    "COM_CD" : "HN",
                    "RESULT" : "无"
                }, {
                    "COM_CD" : "HT",
                    "RESULT" : "无"
                } ]
            }
     * @param dataRoot  
     * @return
     */
    private List getTcCertFltCustDsFromDataRoot(Map dataRoot){
        //当TcCertFltCustD中只有一个元素的时候 是一个对象，不是一个数组
        Object tcCertFltCustDs = dataRoot.get("TcCertFltCustD");
        //edit by zhaojt 2016.09.23 对响应数据结构做兼容处理（当乘机记录为一条时，TcCertFltCustDs子节点被去掉，直接从DataRoot结点拿取数据）
        if(tcCertFltCustDs == null){
            tcCertFltCustDs = dataRoot.get("DataRoot");//兼容处理
        }
        if(tcCertFltCustDs instanceof List){
            return (List)tcCertFltCustDs;
        }
        if(tcCertFltCustDs instanceof Map){
            Map map = (Map)tcCertFltCustDs;
            List list = new ArrayList();
            list.add(map);
            return list;
        }
        return null;
    }
    
    /**
     * 判断航空实名认知接口验证是否成功
     * @param tcCertFltCustDs
     * @return
     */
    private boolean isCertificationSuccess(List tcCertFltCustDs){
        for(Object each : tcCertFltCustDs){
            if(each instanceof Map){
                String result = (String)((Map)each).get("RESULT");
                if(result != null && "是".equals(result)){
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * 根据路径获取json对象节点
     * @param path  对象路径,点号分割
     * @param map   json格式对象
     * @return
     */
    private Object getNodeByPath(String path, Map map){
        if(path == null || map == null){
            return null;
        }
        
        String[] paths = path.split("\\.");
        Object current = map;
        
        int i = 0;
        do{
            String p = paths[i];
            
            if(null == current){
                return null;
            }
            //判断父节点是不是字典
            if(!(current instanceof Map) && i != paths.length - 1){
                return null;
            }
            current = ((Map)current).get(p);
            //判断当前节点是不是字典
            if(!(current instanceof Map) && i != paths.length - 1){
                return null;
            }
            i++;
        }while(i < paths.length);
        
        return current;
    }
    
    /**
     * 执行实名认证请求  
     * @param cust  客户信息
     * @return      响应对象：json字符串
     */
    private String doQueryCertificationReq(CustPersonInfo cust){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String timeStamp = sdf.format(new Date());
        
        String res = 
            new ODPRequest(certificationReqParam.getURL(), certificationReqParam.getAppSecret())
            //系统参数
            .addTextSysPara("Method", certificationReqParam.getMethod())
            .addTextSysPara("AccessToken", certificationReqParam.getAccessToken())//应用票据
            .addTextSysPara("Format", "json")
            .addTextSysPara("Timestamp", timeStamp)
            //应用参数
            .addTextAppPara("SYS_NO", certificationReqParam.getSysNo())
            .addTextAppPara("CERT_TYP_CD", "0")
            .addTextAppPara("CERT_NO", cust.getCertNo())
            .addTextAppPara("NAME", cust.getCustName())
            .addTextAppPara("COM_NO", "0000")//默认查询全部验证通道。  JD,HN,HT
            .post();
        
        log.info("海航实名认证接口响应数据： res = {} ;", LoggerMaskUtils.maskString(res));
        return res;
    }
    
    public int getBatchSize() {
        return batchSize;
    }

    public void setBatchSize(int batchSize) {
        this.batchSize = batchSize;
    }

    public CertificationReqParamVo getCertificationReqParam() {
        return certificationReqParam;
    }

    public void setCertificationReqParam(CertificationReqParamVo certificationReqParam) {
        this.certificationReqParam = certificationReqParam;
    }
    // lx modify 生产bug4期 2017-05-08 17:13:23
    private String simpleCustInfo(CustPersonInfo cust){
        return " custId = " + cust.getCustId() + ", certNo = " + LoggerMaskUtils.maskString(cust.getCertNo())
                + ", certName = " + LoggerMaskUtils.maskNameString(cust.getCustName()) + ";";
    }
    
    private PersCertChannelDTL buildPersCertChannelDTL(CustPersonInfo cust, String channel, boolean isSuccess){
        PersCertChannelDTL persCertChannelDTL = new PersCertChannelDTL();
        persCertChannelDTL.setCustId(cust.getCustId());
        persCertChannelDTL.setCertNo(cust.getCertNo());
        persCertChannelDTL.setChannelCode(channel);
        persCertChannelDTL.setStatus(isSuccess == true ? "01" : "02");
        persCertChannelDTL.setCreatedTs(new Date());
        return persCertChannelDTL;
    }
}
